<!doctype html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
    <title>{{title}}</title>

    {%- if google_font %}
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family={{google_font}}&display=swap"
      rel="stylesheet"
    />
    {{google_font_data}}
    {%- if google_tooltip_font %}
    <link
      href="https://fonts.googleapis.com/css2?family={{google_tooltip_font}}&display=swap"
      rel="stylesheet"
    />
    {%- endif -%} 
    {%- endif %}
    <link
      href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css"
      rel="stylesheet"
    />
    <link
      href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css"
      rel="stylesheet"
    />
    {% if offline_mode %}
    <script>
      // Function to decode and evaluate base64 encoded scripts
      function loadBase64Script(base64Script) {
          const decodedScript = atob(base64Script);
          const script = document.createElement('script');
          script.textContent = decodedScript;
          document.head.appendChild(script);
      }
      {% for url in js_dependency_urls -%}
        // Base64 encoded content from {{url}}
        const {{offline_mode_data[url]["name"]}} = "{{offline_mode_data[url]['encoded_content']}}";
        loadBase64Script({{offline_mode_data[url]["name"]}});
      {% endfor -%}
    </script>
    {% else %}
    {% for url in js_dependency_urls -%}
    <script src="{{ url }}"></script>
    {% endfor -%}
    {% endif -%}  

    {% for _, css_src in css_dependency_srcs.items() -%}
    <style>
      {{ css_src | safe }}
    </style>
    {%- endfor %}
    <style>
      body {
        margin: 0;
        padding: 0;
        overflow: hidden;
        background: {{page_background_color}};
        font-family: {{title_font_family}};
        color: {{title_font_color}};
      }
      .container-box {
        margin: 8px 16px 8px 16px;
        padding: 12px;
        border-radius: 16px;
        line-height: 0.95;
        width: fit-content;
        height: fit-content;
        z-index: 2;
        backdrop-filter: blur(3px);
        background: {{title_background}};
        box-shadow: 2px 3px 10px {{shadow_color}};
        position: relative;
        pointer-events: auto;
      }
      .more-opaque {
        backdrop-filter: blur(6px);
        background-color: {{title_background[:-2] + "ee"}};
      }
      #deck-container {
        width: 100vw;
        height: 100vh;  
      }
      #deck-container canvas {
        z-index: 1;
        background: {{page_background_color}};
      }
      .deck-tooltip {
          {{tooltip_css}}
      }
      input {
        margin: 2px;
        padding: 4px;
        border-radius: 8px;
        color: {{title_font_color}};
        background: {{input_background}};
        border: 2px inset #77777744;
        transition: 0.5s;
        outline: none;
        box-sizing: border-box;
      }
      input:focus {
        border: 2px inset #555;
      }
      {%- if logo %}
      img {
        display: block;
        margin-left: auto;
        margin-right: auto;
      }
      #logo-container {
        position: absolute;
        right: 0;
        bottom: 0;
        margin: 8px 16px 8px 16px;
        z-index: 0;
      }
      {%- endif %}

      {%- if search %}
      #search-container{
        width: fit-content;
      }
      {%- endif %}
       {%- if custom_css %}
      {{custom_css}}
      {%- endif %}
    </style>
  </head>
  <body>
    <div id="loading">
      <img
        id="loading-image"
        src=""
        alt="Loading..."
        width="5%"
      />
    </div>
    <div style="isolation: isolate; position: relative;">
      <div id="deck-container" style="position: fixed; z-index: -1; top: 0; left: 0; width: 100%; height: 100%;"></div>
      <div class="content-wrapper">
        <div class="stack top-left">
          {% if use_title %}
          <div id="title-container" class="container-box">
            <span
              style="font-family:{{title_font_family}};font-size:{{title_font_size}}pt;color:{{title_font_color}}"
            >
              {{title}}
            </span>
            <br />
            <span
              style="font-family:{{title_font_family}};font-size:{{sub_title_font_size}}pt;color:{{sub_title_font_color}}"
            >
              {{sub_title}}
            </span>
          </div>
          {% endif %}

          {% if search %}
          <div id="search-container" class="container-box">
            <input autocomplete="off" type="search" id="text-search" placeholder="🔍" />
          </div>
          {% endif %}
          {% if enable_topic_tree %}
          <div id="topic-tree" class="container-box">
          </div>
          {% endif %}
        </div>
        <div class="stack top-right">
          {% if enable_colormap_selector %}
          <div id="legend-container" class="container-box stack-box" style="display:none;">
          </div>
          {% endif %}
        </div>
        <div class="stack bottom-right">
        </div>
        <div class="stack bottom-left">
          {%- if enable_colormap_selector %}
          <div id="colormap-selector-container" class="container-box stack-box"></div>
          {%- endif -%}
          {%- if enable_histogram %}
          <div id="d3histogram-container" class="container-box stack-box"></div>
          {%- endif -%}
        </div>
      </div>
      {% if logo %}
      <div id="logo-container" class="container-box" style="position: fixed; z-index: 0; bottom: 0; right: 0;">
        <img src="{{logo}}" style="width:{{logo_width}}px" />
      </div>
      {% endif %}
    </div>
    {%- if show_loading_progress %}
    <div id="progress-container" class="datamapplot-progress-container container-box" style="width: 500px; position: absolute;">
      <div id="point-data-progress" class="datamapplot-progress-bar">
        <span class="datamapplot-progress-bar-fill" style="width: 0%;">
          <span class="datamapplot-progress-bar-text">Point Data: 0%</span>
        </span>
      </div>
      <div id="label-data-progress" class="datamapplot-progress-bar">
        <span class="datamapplot-progress-bar-fill" style="width: 0%;">
          <span class="datamapplot-progress-bar-text">Label Data: 0%</span>
        </span>
      </div>
      <div id="meta-data-progress" class="datamapplot-progress-bar">
        <span class="datamapplot-progress-bar-fill" style="width: 0%;">
          <span class="datamapplot-progress-bar-text">Meta Data: 0%</span>
        </span>
      </div>
      {%- if enable_histogram %}
      <div id="histogram-bin-data-progress" class="datamapplot-progress-bar">
        <span class="datamapplot-progress-bar-fill" style="width: 0%;">
          <span class="datamapplot-progress-bar-text">Histogram Bin Data: 0%</span>
        </span>
      </div>
      <div id="histogram-index-data-progress" class="datamapplot-progress-bar">
        <span class="datamapplot-progress-bar-fill" style="width: 0%;">
          <span class="datamapplot-progress-bar-text">Histogram Index Data: 0%</span>
        </span>
      </div>
      {%- endif -%}
      {%- if enable_colormap_selector %}
      <div id="color-data-progress" class="datamapplot-progress-bar">
        <span class="datamapplot-progress-bar-fill" style="width: 0%;">
          <span class="datamapplot-progress-bar-text">Colormap Data: 0%</span>
        </span>
      </div>
      {%- endif %}
    </div>
    {%- endif %}
    {% if custom_html %} {{custom_html}} {%- endif -%}
  </body>
  {%- for _, js_src in js_dependency_srcs.items() %}
  <script>
    {{ js_src | safe }}
  </script>
  {%- endfor %}
  <script type="module">
    function debounce(func, timeout = 250) {
        let timer;
        return (...args) => {
            clearTimeout(timer);
            timer = setTimeout(() => { func.apply(this, args); }, timeout);
        };
    }

    async function simpleArrowParser(arrow_bytes) {
      const table = await Arrow.tableFromIPC(arrow_bytes);
      const result = {};
      table.schema.fields.forEach((field) => {
        result[field.name] = table.getChild(field.name).toArray();
      });
      return result;
    }

    function mergeTypedArrays(arrays) {
        let totalLength = arrays.reduce((acc, arr) => acc + arr.length, 0);
        let result = new arrays[0].constructor(totalLength);
        let currentLength = 0;
        for (let arr of arrays) {
            result.set(arr, currentLength);
            currentLength += arr.length;
        }
        return result;
    }

    {% if not inline_data -%}
    function combineTypedTableChunks(tableChunks) {
      tableChunks.sort((a, b) => a.chunkIndex - b.chunkIndex);
      const combinedTable = {};
      Object.keys(tableChunks[0].chunkData).forEach((key) => {
        const arrays = tableChunks.map((chunk) => chunk.chunkData[key]);
        combinedTable[key] = mergeTypedArrays(arrays);
      });
      return combinedTable;
    }

    function combineTableChunks(tableChunks) {
      tableChunks.sort((a, b) => a.chunkIndex - b.chunkIndex);
      const combinedTable = {};
      Object.keys(tableChunks[0].chunkData).forEach((key) => {
        const arrays = tableChunks.map((chunk) => chunk.chunkData[key]);
        combinedTable[key] = arrays.flat();
      });
      return combinedTable;
    }
    {% endif -%}

    if (!("CompressionStream" in window)) {
      throw new Error(
        "Your browser doesn't support the Compression Streams API " +
          "https://developer.mozilla.org/docs/Web/API/Compression_Streams_API#browser_compatibility",
      );
    }

    {% if inline_data %}
    const pointDataEncoded = "{{base64_point_data}}";
    const hoverDataEncoded = "{{base64_hover_data}}";
    const labelDataEncoded = "{{base64_label_data}}";
    const histogramBinDataEncoded = "{{base64_histogram_bin_data}}";
    const histogramIndexDataEncoded = "{{base64_histogram_index_data}}";
    {% if enable_colormap_selector %}
    const colorDataEncoded = "{{base64_color_data}}";
    {% endif %}
    {% if edge_bundle %}
    const edgeDataEncoded = "{{base64_edge_data}}";
    {% endif %}

    // Blob for the parsing worker
    const parsingWorkerBlob = new Blob([`
      self.onmessage = async function(event) {
      const { encodedData, JSONParse } = event.data;
        // Function to parse base64 to Uint8Array
        async function DecompressBytes(bytes) {
          const blob = new Blob([bytes]);
          const decompressedStream = blob.stream().pipeThrough(
            new DecompressionStream("gzip")
          );
          const arr = await new Response(decompressedStream).arrayBuffer()
          return new Uint8Array(arr);
        }
        {%- if show_loading_progress %}
        async function decodeBase64WithProgress(base64) {
          const totalLength = base64.length;
          const chunkSize = 1024 * 1024; // 1MB chunks
          let decodedArray = new Uint8Array(Math.ceil(totalLength * 3 / 4));
          let offset = 0;

          for (let i = 0; i < totalLength; i += chunkSize) {
            const chunk = base64.slice(i, i + chunkSize);
            const decodedChunk = Uint8Array.from(atob(chunk), c => c.charCodeAt(0));
            decodedArray.set(decodedChunk, offset);
            offset += decodedChunk.length;

            const progress = Math.min(100, Math.round((i + chunkSize) / totalLength * 100));
            self.postMessage({ type: 'progress', progress: progress });

            // Allow other operations to occur
            await new Promise(resolve => setTimeout(resolve, 0));
          }

          return decodedArray.slice(0, offset);
        }
        {%- else %}
        async function decodeBase64WithProgress(base64) {
            return Uint8Array.from(atob(base64), c => c.charCodeAt(0));
        }
        {%- endif %}
        const binaryData = await decodeBase64WithProgress(encodedData).then(buffer => DecompressBytes(buffer));

        if (JSONParse) {
          const parsedData = JSON.parse(new TextDecoder("utf-8").decode(binaryData));
          self.postMessage({ data: parsedData });
        } else {
          // Send the parsed table back to the main thread
          self.postMessage({ type: "data", data: binaryData });
        }
      }
    `], { type: 'application/javascript' });
    const workerUrl = URL.createObjectURL(parsingWorkerBlob);
    {% else %}

    {% if splash_warning %}
    var loadData = false;
    const overlay = document.createElement('div');
    overlay.style.position = 'fixed';
    overlay.style.top = 0;
    overlay.style.left = 0;
    overlay.style.width = '100%';
    overlay.style.height = '100%';
    overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
    overlay.style.zIndex = 1000;
    const splashWarning = document.createElement('div');
    splashWarning.style.position = 'absolute';
    splashWarning.style.top = '50%';
    splashWarning.style.left = '50%';
    splashWarning.style.transform = 'translate(-50%, -50%)';
    splashWarning.style.textAlign = 'center';
    splashWarning.style.padding = '20px';
    splashWarning.style.maxWidth = '480px';
    splashWarning.className = 'container-box';
    function dismissSplash() {
      document.body.removeChild(overlay);
      loadData = true;
    }
    splashWarning.innerHTML = `
      {% if title %}<h2>{{title}}</h2>{% endif %}
      <p>
        {{splash_warning}}
      </p>
    `;
    overlay.appendChild(splashWarning);
    const dismissSplashButton = document.createElement('button');
    dismissSplashButton.textContent = 'Proceed';
    dismissSplashButton.addEventListener('click', dismissSplash);
    dismissSplashButton.style.marginTop = '10px';
    dismissSplashButton.style.padding = '8px 12px';
    dismissSplashButton.style.backgroundColor = 'green';
    dismissSplashButton.style.fontFamily = '{{title_font_family}}';
    dismissSplashButton.style.color = 'white';
    dismissSplashButton.style.border = 'none';
    dismissSplashButton.style.cursor = 'pointer';
    dismissSplashButton.style.borderRadius = '8px';
    splashWarning.appendChild(dismissSplashButton);
    document.body.appendChild(overlay);

    while (!loadData) {
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
    {% endif %}

    const currentURL = self.location.pathname;
    const directoryPath = currentURL.substring(0, currentURL.lastIndexOf('/') + 1);
    const originURL = self.location.origin + directoryPath;

    const pointDataEncoded = [
      {% for chunk_index in range(n_data_chunks) -%}
      `${originURL}/{{file_prefix}}_point_data_{{chunk_index}}.zip`,
      {% endfor -%}
    ];
    const hoverDataEncoded = [
      {% for chunk_index in range(n_data_chunks) -%}
      `${originURL}/{{file_prefix}}_meta_data_{{chunk_index}}.zip`,
      {% endfor -%}
    ];
    const labelDataEncoded = [`${originURL}/{{file_prefix}}_label_data.zip`];
    {% if enable_histogram %}
    const histogramBinDataEncoded = [`${originURL}/{{file_prefix}}_histogram_bin_data.zip`];
    const histogramIndexDataEncoded = [`${originURL}/{{file_prefix}}_histogram_index_data.zip`];
    {% endif %}
    {% if enable_colormap_selector %}
    const colorDataEncoded = [
      {% for chunk_index in range(n_data_chunks) -%}
      `${originURL}/{{file_prefix}}_color_data_{{chunk_index}}.zip`,
      {% endfor -%}
    ];
    {% endif %}
    {% if edge_bundle %}
    const edgeDataEncoded = [`${originURL}/{{file_prefix}}_edge_data.zip`];
    {% endif %}

    // Blob for the parsing worker
    const parsingWorkerBlob = new Blob([`
      self.onmessage = async function(event) {
        const { encodedData, JSONParse } = event.data;
        async function decompressFile(filename) {
          try {
            const response = await fetch(filename);
            if (!response.ok) {
              throw new Error(\`HTTP error! status: \${response.status}. Failed to fetch: \${filename}\`);
            }
            const reader = response.body
              .pipeThrough(new DecompressionStream("gzip"))
              .getReader();

            let chunks = [];
            let totalSize = 0;

            while (true) {
              const { done, value } = await reader.read();
              if (done) {
                break;
              }
              chunks.push(value);
              totalSize += value.length;
            }

            // Concatenate chunks into a single Uint8Array
            const decompressedData = new Uint8Array(totalSize);
            let position = 0;
            for (const chunk of chunks) {
              decompressedData.set(chunk, position);
              position += chunk.length;
            }

            return decompressedData;
          } catch (error) {
            console.error('Decompression failed:', error);
            throw error;
          }
        }
        let processedCount = 0;
        const decodedData = encodedData.map(async (file, i) => {
          const binaryData = await decompressFile(file);
          processedCount += 1;
          self.postMessage({ type: "progress", progress: Math.round(((processedCount) / encodedData.length) * 95) });

          if (JSONParse) {
            const parsedData = JSON.parse(new TextDecoder("utf-8").decode(binaryData));
            return { chunkIndex: i, chunkData: parsedData };
          } else {
            return { chunkIndex: i, chunkData: binaryData };
          }
        });
        self.postMessage({ type: "data", data: await Promise.all(decodedData) });
      }
    `], { type: 'application/javascript' });
    const workerUrl = URL.createObjectURL(parsingWorkerBlob);
    {% endif -%}

    const searchItemId = "text-search";
    const histogramItemId = "d3histogram-container";
    const selectionItemId = "lasso-select";
    const searchItem = document.getElementById(searchItemId);
    let histogramItem = null;
    
    const container = document.getElementById('deck-container');

    const labelDataWorker = new Worker(workerUrl);
    const pointDataWorker = new Worker(workerUrl);
    const metaDataWorker = new Worker(workerUrl);
    {% if enable_histogram %}
    const histogramBinDataWorker = new Worker(workerUrl);
    const histogramIndexDataWorker = new Worker(workerUrl);
    {% endif -%}
    {% if enable_colormap_selector %}
    const colorDataWorker = new Worker(workerUrl);
    {% endif %}
    {% if edge_bundle %}
    const edgeDataWorker = new Worker(workerUrl);
    {% endif %}

    const datamap = new DataMap({
      container: container,
      bounds: {{data_bounds}},
      searchItemId: searchItemId,
      lassoSelectionItemId: selectionItemId,      
    });

    function updateProgressBar(id, progress) {
      const progressBar = document.querySelector(`#${id} .datamapplot-progress-bar-fill`);
      const progressText = document.querySelector(`#${id} .datamapplot-progress-bar-text`);
      progressBar.style.width = `${progress}%`;
      progressText.textContent = `${id.replace('-progress', '').replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}: ${progress}%`;
    }

    function checkAllDataLoaded() {
      const progressBars = document.querySelectorAll('.datamapplot-progress-bar-fill');
      const allLoaded = Array.from(progressBars).every(bar => bar.style.width === '100%');
      if (allLoaded) {
        document.getElementById("loading").style.display = "none";
        document.getElementById("progress-container").style.display = "none";
      }
    }

    function loadPointDataLayer() {
      pointDataWorker.postMessage({encodedData: pointDataEncoded, JSONParse: false});

      pointDataWorker.onmessage = async function(event) {
        if (event.data.type === "progress") {
          updateProgressBar('point-data-progress', event.data.progress);
        } else {
          const { data } = event.data;
          {% if inline_data %}
          const pointData = await simpleArrowParser(data);
          {% else %}
          const chunkArray = data.map(async ({ chunkIndex, chunkData }) => {
            return {chunkIndex: chunkIndex, chunkData: await simpleArrowParser(chunkData)};
          });
          const pointData = await Promise.all(chunkArray).then(combineTypedTableChunks);
          {% endif %}
          datamap.addPoints(
            pointData,
            {
              pointSize: {{point_size}},
              pointOutlineColor: {{point_outline_color}},
              pointLineWidth: {{point_line_width}},
              pointHoverColor: {{point_hover_color}},
              pointLineWidthMaxPixels: {{point_line_width_max_pixels}},
              pointLineWidthMinPixels: {{point_line_width_min_pixels}},
              pointRadiusMaxPixels: {{point_radius_max_pixels}},
              pointRadiusMinPixels: {{point_radius_min_pixels}},
            }
          );

          document.getElementById("loading").style.display = "none";
          updateProgressBar('point-data-progress', 100);
          checkAllDataLoaded();
          
          {%- if enable_lasso_selection %}
          /* Lasso Selection */
          const lassoSelector = new LassoSelectionTool(
            datamap,
          );
          datamap.lassoSelector = lassoSelector;
          {% endif -%}
        }
      };

      {% if enable_colormap_selector %}
      colorDataWorker.postMessage({encodedData: colorDataEncoded, JSONParse: false});

      colorDataWorker.onmessage = async function(event) {
        if (event.data.type === "progress") {
          updateProgressBar('color-data-progress', event.data.progress);
          // console.log("Color data progress: ", event.data.progress);
        } else {
          const { data } = event.data;
          {% if inline_data %}
          const colorData = await simpleArrowParser(data);
          {% else %}
          const chunkArray = data.map(async ({ chunkIndex, chunkData }) => {
            return {chunkIndex: chunkIndex, chunkData: await simpleArrowParser(chunkData)};
          });
          const colorData = await Promise.all(chunkArray).then(combineTypedTableChunks);
          {% endif %}

          document.getElementById("loading").style.display = "none";
          updateProgressBar('color-data-progress', 100);
          checkAllDataLoaded();

          const colorMapContainer = document.getElementById("colormap-selector-container");
          const legendContainer = document.getElementById("legend-container");
          
          const colorMaps = {{colormap_metadata}};
          const colorSelector = new ColormapSelectorTool(
            colorMaps,
            colorMapContainer,
            colorData,
            legendContainer,
            datamap,
          );
          datamap.colorSelector = colorSelector;
        }
      };
      {% endif %}

      {% if edge_bundle %}
      edgeDataWorker.postMessage({encodedData: edgeDataEncoded, JSONParse: false});

      edgeDataWorker.onmessage = async function(event) {
        if (event.data.type === "progress") {
          updateProgressBar('edge-data-progress', event.data.progress);
        } else {
          const { data } = event.data;
          {% if inline_data %}
          const edgeData = await simpleArrowParser(data);
          {% else %}
          const chunkArray = data.map(async ({ chunkIndex, chunkData }) => {
            return {chunkIndex: chunkIndex, chunkData: await simpleArrowParser(chunkData)};
          });
          const edgeData = await Promise.all(chunkArray).then(combineTypedTableChunks);
          {% endif %}

          datamap.addEdges(
            edgeData,
            {
              edgeWidth: {{edge_width}},
            }
          );

          document.getElementById("loading").style.display = "none";
          updateProgressBar('edge-data-progress', 100);
          checkAllDataLoaded();
        }
      };
      {% endif %}
    }

    function loadLabelDataLayer() {
      labelDataWorker.postMessage({encodedData: labelDataEncoded, JSONParse: true});

      labelDataWorker.onmessage = async function(event) {
        if (event.data.type === "progress") {
          updateProgressBar('label-data-progress', event.data.progress);
        } else {
          const { data } = event.data;
          {% if inline_data -%}
          const labelData = data;
          {% else -%}
          const labelData = data[0].chunkData;
          {% endif -%}
          datamap.labelData = labelData;
          // Only build labels where we have a position.
          datamap.addLabels(
            {%- if enable_topic_tree -%}
            labelData.filter((d) => {return !(d.id && d.id.endsWith('-1'))})
            {%- else -%}
            labelData
            {%- endif -%}, {
            labelTextColor: {{label_text_color}},
            textMinPixelSize: {{text_min_pixel_size}},
            textMaxPixelSize: {{text_max_pixel_size}},
            textOutlineWidth: {{text_outline_width}},
            textOutlineColor: {{text_outline_color}},
            textBackgroundColor: {{text_background_color}},
            fontFamily: "{{font_family}}",
            fontWeight: {{font_weight}},
            lineSpacing: {{line_spacing}},
            textCollisionSizeScale: {{text_collision_size_scale}},
            noiseLabel: "{{noise_label}}",
            pickable: false,
          });

          {% if enable_topic_tree %}
            const topicTreeContainer = document.querySelector('#topic-tree');
            
            {% if topic_tree_button_on_click != 'null' %}
            const topicTree = new TopicTree(
                topicTreeContainer,
                datamap,
                true,
                {{topic_tree_button_icon}},
                {
                  title:{{topic_tree_title}},
                  maxWidth:{{topic_tree_max_width}},
                  maxHeight:{{topic_tree_max_height}},
                  fontSize:{{topic_tree_font_size}},
                  colorBullets:{{topic_tree_color_bullets}},
                }             
            );
              
            // Set up the topic tree button behaviour.
            topicTree.container.querySelectorAll('.topic-tree-btn').forEach(button => {
                button.addEventListener('click', function() {
                    var label = datamap.labelData.find(l => {
                      return l.id === this.dataset.labelId
                    })
                    {{topic_tree_button_on_click[1:-1]}}
                });
            });
            {% else %}
            const topicTree = new TopicTree(
                topicTreeContainer,
                datamap,
                false,
                null,
                {
                  title:{{topic_tree_title}},
                  maxWidth:{{topic_tree_max_width}},
                  maxHeight:{{topic_tree_max_height}},
                  fontSize:{{topic_tree_font_size}},
                  colorBullets:{{topic_tree_color_bullets}},
                }           
            );
            {% endif -%}
            const debouncedViewStateChange = debounce(({viewState, interactionState}) => {
                const userIsInteracting = Object.values(interactionState).every(Boolean);
                if (!userIsInteracting) {
                    const visible = getVisibleTextData(viewState)
                    if (visible) {
                        topicTree.highlightElements(visible)
                    };
                };
            }, 150);
            
            datamap.deckgl.setProps({
                onViewStateChange: debouncedViewStateChange,
            });

          {% endif %}
          {% if cluster_boundary_polygons %}
          datamap.addBoundaries(labelData.filter((d) => {
                return d.polygon
            }), {
            clusterBoundaryLineWidth: {{cluster_boundary_line_width}},
          });
          {% endif %}
          document.getElementById("loading").style.display = "none";
          updateProgressBar('label-data-progress', 100);
          checkAllDataLoaded();
        }
      };
    }

    function loadMetaData() {
      metaDataWorker.postMessage({encodedData: hoverDataEncoded, JSONParse: true});

      metaDataWorker.onmessage = async function(event) {
        if (event.data.type === "progress") {
          updateProgressBar('meta-data-progress', event.data.progress);
        } else {
          const { data } = event.data;
          {% if inline_data -%}
          const hoverData = data;
          {% else -%}
          const hoverData = combineTableChunks(data);
          {% endif -%}
          datamap.addMetaData(hoverData, {
            tooltipFunction: {{get_tooltip}},
            onClickFunction: {{on_click if on_click else "null"}},
            searchField: "{{search_field}}",
          });

          {% if search %}
          /* Search */
          searchItem.addEventListener("input", debounce(event => datamap.searchText(event.target.value)));
          {% endif -%}
          updateProgressBar('meta-data-progress', 100);
          checkAllDataLoaded();
        }
      };
    }

    {% if enable_histogram %}
    function loadHistogramBinData() {
      return new Promise((resolve, reject) => {
        histogramBinDataWorker.postMessage({encodedData: histogramBinDataEncoded, JSONParse: true});

        histogramBinDataWorker.onmessage = async function(event) {
          if (event.data.type === "progress") {
            updateProgressBar('histogram-bin-data-progress', event.data.progress);
          } else {
            const { data } = event.data;
            {% if inline_data -%}
            const histogramBinData = data;
            {% else -%}
            const histogramBinData = data[0].chunkData;
            {% endif -%}
            resolve(histogramBinData);
            updateProgressBar('histogram-bin-data-progress', 100);
            checkAllDataLoaded();
          }
        };
      });
    }

    function loadHistogramIndexData() {
      return new Promise((resolve, reject) => {
        histogramIndexDataWorker.postMessage({encodedData: histogramIndexDataEncoded, JSONParse: false});

        histogramIndexDataWorker.onmessage = async function(event) {
          if (event.data.type === "progress") {
            updateProgressBar('histogram-index-data-progress', event.data.progress);
          } else {
            const { data } = event.data;
            {% if inline_data -%}
            const histogramIndexData = simpleArrowParser(data);
            {% else -%}
            const histogramIndexData = simpleArrowParser(data[0].chunkData);
            {% endif -%}
            resolve(histogramIndexData);
            updateProgressBar('histogram-index-data-progress', 100);
            checkAllDataLoaded();
          }
        };
      });
    }

    function loadHistogramData() {
      const chartSelectionCallback = chartSelectedIndices => {
        // Update data manager
        if (chartSelectedIndices === null) {
          datamap.removeSelection(histogramItemId);
        } else {
          datamap.addSelection(chartSelectedIndices, histogramItemId);
        }
      };

      Promise.all([loadHistogramBinData(), loadHistogramIndexData()]).then(([histogramBinData, histogramIndexData]) => {
        const histogramData = { rawBinData: histogramBinData, rawIndexData: histogramIndexData };
        histogramItem = D3Histogram.create({
          data: histogramData, 
          chartContainerId: histogramItemId,
          {%- if histogram_width %}
          chartWidth: {{ histogram_width }},
          {%- endif %}
          {%- if histogram_height %}
          chartHeight: {{ histogram_height }},
          {%- endif %}
          {%- if histogram_bin_count %}
          binCount: {{ histogram_bin_count }},
          {%- endif %}
          {%- if histogram_title %}
          title: "{{ histogram_title }}",
          {%- endif %}
          enableClickPersistence: {{ histogram_enable_click_persistence|lower }},
          {%- if histogram_bin_fill_color %}
          binDefaultFillColor: "{{ histogram_bin_fill_color }}",
          {%- endif %}
          {%- if histogram_bin_selected_fill_color %}
          binSelectedFillColor: "{{ histogram_bin_selected_fill_color }}",
          {%- endif %}
          {%- if histogram_bin_unselected_fill_color %}
          binUnselectedFillColor: "{{ histogram_bin_unselected_fill_color }}",
          {%- endif %}
          {%- if histogram_bin_context_fill_color %}
          binContextFillColor: "{{ histogram_bin_context_fill_color }}",
          {%- endif %}
          {%- if histogram_log_scale %}
          logScale: {{ histogram_log_scale|lower }},
          {%- endif %}
          chartSelectionCallback: chartSelectionCallback,
        });

        datamap.connectHistogram(histogramItem);
      });
    }
    {% endif -%}

    {% if enable_topic_tree %}
    function isBoundsVisible({bounds, viewState}) {
        const {width, height, longitude, latitude, zoom} = viewState;
    
        const Viewport = new deck.WebMercatorViewport({
            width,
            height,
            longitude,
            latitude,
            zoom,
        })
    
        const viewBounds = [
            [0,0],
            [width, height],
        ];
    
        const minBounds = Viewport.project([bounds[0], bounds[2]]);
        const maxBounds = Viewport.project([bounds[1], bounds[3]]);
    
        return (
            ((minBounds[0] >= 0 && minBounds[0] <= width) || (maxBounds[0] >= 0 && maxBounds[0] <= width)) &&
            ((minBounds[1] >= 0 && minBounds[1] <= height) || (maxBounds[1] >= 0 && maxBounds[1] <= height))
        );
    };
          
    function getVisibleTextData(viewState) {
        if (datamap.labelData) {
            const visibleData = datamap.labelData.filter((d) => {
                return isBoundsVisible({
                    bounds: d.bounds,
                    viewState: viewState,
                });
            });
            return visibleData;
        };
    };
    
    {% endif %}
      
    {%- if background_image -%}
    datamap.addBackgroundImage("{{background_image}}", {{background_image_bounds}});
    {%- endif %}

    {%- if enable_colormap_selector %}
    {% endif -%}

    loadPointDataLayer();
    loadLabelDataLayer();
    loadMetaData();
    {%- if enable_histogram %}
    loadHistogramData();
    {% endif -%}
    {%- if enable_api_tooltip %}
    const tooltip = new DynamicTooltipManager(
      datamap,
      {
        {%- if tooltip_identifier_js %}getIdentifier: {{tooltip_identifier_js}},{% endif -%}
        fetchData: {{tooltip_fetch_js}},
        formatContent: {{tooltip_format_js}},
        formatLoading: {{tooltip_loading_js}},
        formatError: {{tooltip_error_js}},
      }
    );
    {% endif -%}
    {% if custom_js %}
    {{ custom_js }}
    {% endif -%}
  </script>
</html>
